/************************************************************
** @brief   : drv_rtc
** @author  : vandoul
** @github  : https://gitee.com/vandoul
** @date    : 2022-01
** @version : v1.0.0
** @note    : drv_rtc.c
***********************************************************/
#include <rtthread.h>
#include <rtdevice.h>
#include <n32g45x_rtc.h>
#include <n32g45x_bkp.h>
#include <time.h>
#include "n32g45x_bkp_reg.h"

static struct rt_device drv_rtc_dev;

static time_t drv_rtc_hw_get_time()
{
    struct tm t;
    time_t ret;
    RTC_DateType  RTC_DateStructure;
    RTC_TimeType  RTC_TimeStructure;
    RTC_GetDate(RTC_FORMAT_BIN, &RTC_DateStructure);
    RTC_GetTime(RTC_FORMAT_BIN, &RTC_TimeStructure);
    t.tm_year = RTC_DateStructure.Year + 100; //2000 to 1900
    t.tm_mon = RTC_DateStructure.Month - 1;
    t.tm_mday = RTC_DateStructure.Date;
    t.tm_hour = RTC_TimeStructure.Hours;
    t.tm_min = RTC_TimeStructure.Minutes;
    t.tm_sec = RTC_TimeStructure.Seconds;
    ret = mktime(&t);
//    rt_kprintf("get %d-%d-%d %d:%d:%d\r\n", RTC_DateStructure.Year, RTC_DateStructure.Month, RTC_DateStructure.Date, RTC_TimeStructure.Hours, RTC_TimeStructure.Minutes, RTC_TimeStructure.Seconds);
//    rt_kprintf("rtc get %d\r\n", ret);
    return ret;
}
static void drv_rtc_hw_set_time(time_t t)
{
    struct tm *ltm;
    rt_kprintf("rtc set %d\r\n", t);
    ltm = localtime(&t);
    RTC_DateType  RTC_DateStructure = {0};
    RTC_TimeType  RTC_TimeStructure = {0};
    RTC_DateStructure.Year = ltm->tm_year - 100;
    RTC_DateStructure.Month = ltm->tm_mon + 1;
    RTC_DateStructure.Date = ltm->tm_mday;
    if(ltm->tm_wday == 0) {
        RTC_DateStructure.WeekDay = RTC_WEEKDAY_SUNDAY;
    } else {
        RTC_DateStructure.WeekDay = ltm->tm_wday;
    }
    RTC_TimeStructure.Hours = ltm->tm_hour;
    RTC_TimeStructure.Minutes = ltm->tm_min;
    RTC_TimeStructure.Seconds = ltm->tm_sec;
    rt_kprintf("set %d-%d-%d %d:%d:%d\r\n", RTC_DateStructure.Year, RTC_DateStructure.Month, RTC_DateStructure.Date, RTC_TimeStructure.Hours, RTC_TimeStructure.Minutes, RTC_TimeStructure.Seconds);
    PWR_BackupAccessEnable(ENABLE);
    if(RTC_SetDate(RTC_FORMAT_BIN, &RTC_DateStructure) != SUCCESS) {
        rt_kprintf("rtc set date failed\r\n");
        PWR_BackupAccessEnable(DISABLE);
        return ;
    }
    if(RTC_ConfigTime(RTC_FORMAT_BIN, &RTC_TimeStructure) != SUCCESS) {
        rt_kprintf("rtc set time failed\r\n");
        PWR_BackupAccessEnable(DISABLE);
        return ;
    }
    PWR_BackupAccessEnable(DISABLE);
}
static rt_err_t drv_rtc_control(rt_device_t dev, int cmd, void *args)
{
    time_t *t;

    RT_ASSERT(dev != RT_NULL);

    switch (cmd)
    {
    case RT_DEVICE_CTRL_RTC_GET_TIME:
        t = (time_t *) args;
        *t = drv_rtc_hw_get_time();
        break;
    case RT_DEVICE_CTRL_RTC_SET_TIME:
    {
        t = (time_t *) args;
        drv_rtc_hw_set_time(*t);
#ifdef RT_USING_ALARM
        soft_rtc_alarm_update(&wkalarm);
#endif
        break;
    }
#ifdef RT_USING_ALARM
    case RT_DEVICE_CTRL_RTC_GET_ALARM:
        *((struct rt_rtc_wkalarm *)args) = wkalarm;
        break;
    case RT_DEVICE_CTRL_RTC_SET_ALARM:
        wkalarm = *((struct rt_rtc_wkalarm *)args);
        soft_rtc_alarm_update(&wkalarm);
        break;
#endif
    }

    return RT_EOK;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops soft_rtc_ops =
{
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    drv_rtc_control
};
#endif
#define RTCCLK_USE_LSE
#define RCC_OFFSET (RCC_BASE - PERIPH_BASE)
#define CTRLSTS_OFFSET   (RCC_OFFSET + 0x24)
#define LSIEN_BITNUMBER  0x00
#define CTRLSTS_LSIEN_BB (PERIPH_BB_BASE + (CTRLSTS_OFFSET * 32) + (LSIEN_BITNUMBER * 4))
static int drv_rtc_hw_init(int force)
{
    RTC_InitType RTC_InitStructure;

    RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_PWR | RCC_APB1_PERIPH_BKP, ENABLE);

    /* Allow access to RTC */
    PWR_BackupAccessEnable(ENABLE);
    if((BKP_ReadBkpData(BKP_DAT1) == 0xAA55) && (!force)) {
        rt_kprintf("rtc is initialized\r\n");
        PWR_BackupAccessEnable(DISABLE);
        return 0;
    }
    /* Disable RTC clock */
    RCC_EnableRtcClk(DISABLE);
    BKP_WriteBkpData(BKP_DAT1, 0xAA55);
    rt_kprintf("BKP_DAT1 = %04x\r\n", BKP_ReadBkpData(BKP_DAT1));
    #ifdef RTCCLK_USE_LSE
    RCC_ConfigLse(ENABLE);
    while(RCC_GetFlagStatus(RCC_FLAG_LSERD) == RESET);
    //LSE=32.768KHz
    //fck_apre = frtcclk / (async + 1)
    //fck_spre = frtcclk / ((sync + 1) * (async + 1))
    RCC_ConfigRtcClk(RCC_RTCCLK_SRC_LSE);
    RTC_InitStructure.RTC_HourFormat = RTC_24HOUR_FORMAT;
    RTC_InitStructure.RTC_SynchPrediv = 255;
    RTC_InitStructure.RTC_AsynchPrediv = 127;
    #else
    rt_kprintf("BDCTRL:%08x, LSIEN:%d\r\n", RCC->BDCTRL, *(__IO uint32_t*)CTRLSTS_LSIEN_BB);
    RCC_EnableLsi(ENABLE);
    while(RCC_GetFlagStatus(RCC_FLAG_LSIRD) == RESET);
    //LSI=40KHz
    //fck_apre = frtcclk / (async + 1)
    //fck_spre = frtcclk / ((sync + 1) * (async + 1))
    RCC_ConfigRtcClk(RCC_RTCCLK_SRC_LSI);
    rt_kprintf("BDCTRL:%08x, LSIEN:%d\r\n", RCC->BDCTRL, *(__IO uint32_t*)CTRLSTS_LSIEN_BB);
    RTC_InitStructure.RTC_HourFormat = RTC_24HOUR_FORMAT;
    RTC_InitStructure.RTC_SynchPrediv = 399;
    RTC_InitStructure.RTC_AsynchPrediv = 99;
    #endif
    if(RTC_Init(&RTC_InitStructure)) {
        rt_kprintf("rtc init failed!\r\n");
        PWR_BackupAccessEnable(DISABLE);
        return -1;
    }
    RCC_EnableRtcClk(ENABLE);
    RTC_WaitForSynchro();
    rt_kprintf("rtc hw init ok!\r\n");
    PWR_BackupAccessEnable(DISABLE);
    return 0;
}

int rtc_force_init(int argc, char *argv[])
{
    return drv_rtc_hw_init(1);
}

MSH_CMD_EXPORT_ALIAS(rtc_force_init, rtc_init, init rtc.);

static int drv_rtc_init(void)
{
    static rt_bool_t init_ok = RT_FALSE;

    if (init_ok)
    {
        return 0;
    }
    /* make sure only one 'rtc' device */
    RT_ASSERT(!rt_device_find("rtc"));

#ifdef RT_USING_ALARM
    rt_timer_init(&alarm_time,
                  "alarm",
                  alarm_timeout,
                  &soft_rtc_dev,
                  0,
                  RT_TIMER_FLAG_SOFT_TIMER|RT_TIMER_FLAG_ONE_SHOT);
#endif

    drv_rtc_hw_init(0);

    drv_rtc_dev.type    = RT_Device_Class_RTC;

    /* register rtc device */
#ifdef RT_USING_DEVICE_OPS
    drv_rtc_dev.ops     = &soft_rtc_ops;
#else
    drv_rtc_dev.init    = RT_NULL;
    drv_rtc_dev.open    = RT_NULL;
    drv_rtc_dev.close   = RT_NULL;
    drv_rtc_dev.read    = RT_NULL;
    drv_rtc_dev.write   = RT_NULL;
    drv_rtc_dev.control = drv_rtc_control;
#endif

    /* no private */
    drv_rtc_dev.user_data = RT_NULL;

    rt_device_register(&drv_rtc_dev, "rtc", RT_DEVICE_FLAG_RDWR);

    init_ok = RT_TRUE;

    return 0;
}
INIT_DEVICE_EXPORT(drv_rtc_init);
